//---------------------------------------------------------------------
// File: FactBasedRuleEngineStep.cs
// 
// Summary: 
//
//---------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//---------------------------------------------------------------------
namespace Microsoft.Services.BizTalkApplicationFramework.BizUnit
{
    using System;
    using System.IO;
    using System.Xml;
    using Microsoft.RuleEngine;
    using System.Data.SqlClient;
    using System.Data;

    /// <summary>
    /// Summary description for FactBasedRuleEngineStep.
    /// </summary>
    /// 
    /// <remarks>
    /// The following shows an example of the Xml representation of this test step.
    ///
    /// <code escaped="true">
    ///	<TestStep assemblyPath="" typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.FactBasedRuleEngineStep, Microsoft.Services.BizTalkApplicationFramework.BizUnit.BizTalkSteps">
    ///		<RuleStoreName>C:\Program Files\Microsoft BizTalk Server 2004\SDK\Utilities\TestFramework\SDK\SampleSolution\Test\BiztalkFunctionalTests\RulesTestCases\LoanProcessing.xml</RuleStoreName>
    ///     <RuleSetInfoCollectionName>LoanProcessing</RuleSetInfoCollectionName>
    ///     <DebugTracking>C:\Program Files\Microsoft BizTalk Server 2004\SDK\Utilities\TestFramework\SDK\SampleSolution\Test\BiztalkFunctionalTests\RulesTestCases\outputtraceforLoanPocessing.txt</DebugTracking>
    ///     <ResultFilePath>C:\Program Files\Microsoft BizTalk Server 2004\SDK\Utilities\TestFramework\SDK\SampleSolution\Test\BiztalkFunctionalTests\RulesTestCases</ResultFilePath>
    ///     <Facts>
    ///        <Fact type="document" schemaType="LoanProcessing" instanceDocument="C:\Program Files\Microsoft BizTalk Server 2004\SDK\Utilities\TestFramework\SDK\SampleSolution\Test\BiztalkFunctionalTests\RulesTestCases\SampleLoan.xml"/>
    ///        <Fact type="object" assemblyPath="C:\Program Files\Microsoft Biztalk Server\SDK\Samples\Business Rules\Business Rules Hello World1\MySampleLibrary\bin\Debug\MySampleLibrary.dll" typeName="Microsoft.Samples.BizTalk.BusinessRulesHelloWorld1.MySampleLibrary.MySampleBusinessObject"/>
    ///        <Fact type="dataConnection" connectionString="" dataset="" tableName=""/>
    ///        <Fact type="dataTable" connectionString="" command="" dataset="" tableName=""/>
    ///        <Fact type="dataRow" connectionString="" command="" dataset="" tableName=""/>
    ///     </Facts>
    /// 
    ///		<!-- Note: Validation step could be any generic validation step -->	
    ///		<ValidationStep assemblyPath="" typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.XmlValidationStep">
    ///			<XmlSchemaPath>.\TestData\PurchaseOrder.xsd</XmlSchemaPath>
    ///			<XmlSchemaNameSpace>http://SendMail.PurchaseOrder</XmlSchemaNameSpace>
    ///			<XPathList>
    ///				<XPathValidation query="/*[local-name()='PurchaseOrder' and namespace-uri()='http://SendMail.PurchaseOrder']/*[local-name()='PONumber' and namespace-uri()='']">PONumber_0</XPathValidation>
    ///			</XPathList>
    ///		</ValidationStep>			
    /// 
    ///	</TestStep>
    ///	</code>
    ///	
    ///	<list type="table">
    ///		<listheader>
    ///			<term>Tag</term>
    ///			<description>Description</description>
    ///		</listheader>
    ///		<item>
    ///			<term>RuleStoreName</term>
    ///			<description>The location of the rule store</description>
    ///		</item>
    ///		<item>
    ///			<term>RuleSetInfoCollectionName</term>
    ///			<description>The name of the rule set collection</description>
    ///		</item>
    ///		<item>
    ///			<term>DebugTracking</term>
    ///			<description>Location of the debug tracking</description>
    ///		</item>
    ///		<item>
    ///			<term>ResultFilePath</term>
    ///			<description>The path used to write updated fact documents to</description>
    ///		</item>
    ///		<item>
    ///			<term>Facts</term>
    ///			<description>Facts to pass to rules engine prior to ruleset execution</description>
    ///		</item>	
    ///	</list>
    ///	</remarks>

    public class FactBasedRuleEngineStep : ITestStep
    {
        /// <summary>
        /// ITestStep.Execute() implementation
        /// </summary>
        /// <param name='testConfig'>The Xml fragment containing the configuration for this test step</param>
        /// <param name='context'>The context for the test, this holds state that is passed beteen tests</param>
        public void Execute(System.Xml.XmlNode testConfig, Context context)
        {
            // Retrieve Rule-Set from Policy file
            string RuleStoreName = context.ReadConfigAsString(testConfig, "RuleStoreName");
            string RuleSetInfoCollectionName = context.ReadConfigAsString(testConfig, "RuleSetInfoCollectionName");
            string DebugTracking = context.ReadConfigAsString(testConfig, "DebugTracking");
            XmlNodeList factsList = testConfig.SelectNodes("Facts/*");
            System.IO.FileInfo fi = new System.IO.FileInfo(RuleStoreName);
            XmlNode validationConfig = testConfig.SelectSingleNode("ValidationStep");

            if (!fi.Exists)
            {
                throw new FileNotFoundException("RuleStoreName", RuleStoreName);
            }
            RuleStore ruleStore = new FileRuleStore(fi.FullName);
            RuleSetInfoCollection rsInfo = ruleStore.GetRuleSets(RuleSetInfoCollectionName, RuleStore.Filter.Latest);
            if (rsInfo.Count != 1)
            {
                // oops ... error
                throw new ApplicationException(String.Format("RuleStore {0} did not contain RuleSet {1}", RuleStoreName, RuleSetInfoCollectionName));
            }

            RuleSet ruleset = ruleStore.GetRuleSet(rsInfo[0]);

            // Create an instance of the DebugTrackingInterceptor
            DebugTrackingInterceptor dti = new DebugTrackingInterceptor(DebugTracking);

            // Create an instance of the Policy Tester class
            PolicyTester policyTester = new PolicyTester(ruleset);

            // load the facts into array
            object[] facts = new object[factsList.Count];
            int i = 0;
            System.Type type;

            foreach (XmlNode fact in factsList)
            {
                switch (fact.SelectSingleNode("@type").Value)
                {
                    case "object":
                        {
                            string assemblyPath = fact.Attributes.GetNamedItem("assemblyPath").Value;
                            string typeName = fact.Attributes.GetNamedItem("typeName").Value;

                            #region Simple (string-only) constructor argument handling added by Ian Picknell
                            object[] objectArgs = null;
                            if (null != fact.Attributes.GetNamedItem("args"))
                            {
                                objectArgs = fact.Attributes.GetNamedItem("args").Value.Split(new char[] { ',' });
                            }
                            #endregion

                            System.Reflection.Assembly asm;
                            if (assemblyPath.Length > 0)
                            {
                                asm = System.Reflection.Assembly.LoadWithPartialName(assemblyPath);
                                if (asm == null)
                                    // fail
                                    throw (new Exception("failed to create type " + typeName));
                                type = asm.GetType(typeName, true, false);
                            }
                            else
                            {
                                // must be in path
                                type = Type.GetType(typeName);
                            }
                            facts[i] = Activator.CreateInstance(type, objectArgs);
                            break;
                        }
                    case "document":
                        {
                            string SampleXML = fact.SelectSingleNode("@instanceDocument").Value;
                            XmlDocument xd1 = new XmlDocument();
                            xd1.Load(SampleXML);
                            TypedXmlDocument TXD = new TypedXmlDocument(fact.SelectSingleNode("@schemaType").Value, xd1);
                            facts[i] = TXD;
                            break;
                        }
                    case "dataConnection":
                        {
                            string connection = fact.Attributes.GetNamedItem("connectionString").Value;
                            string datasetName = fact.Attributes.GetNamedItem("dataset").Value;
                            string tableName = fact.Attributes.GetNamedItem("tableName").Value;
                            SqlConnection conn = new SqlConnection(connection);
                            conn.Open();
                            DataConnection dc = new DataConnection(datasetName, tableName, conn);
                            facts[i] = dc;
                            break;
                        }
                    case "dataTable":
                    case "dataRow":
                        {
                            string connection = fact.Attributes.GetNamedItem("connectionString").Value;
                            string command = fact.Attributes.GetNamedItem("command").Value;
                            string datasetName = fact.Attributes.GetNamedItem("dataset").Value;
                            string tableName = fact.Attributes.GetNamedItem("tableName").Value;
                            SqlDataAdapter dAdapt = new SqlDataAdapter();
                            dAdapt.TableMappings.Add("Table", tableName);
                            SqlConnection conn = new SqlConnection(connection);
                            conn.Open();
                            SqlCommand myCommand = new SqlCommand(command, conn);
                            myCommand.CommandType = CommandType.Text;
                            dAdapt.SelectCommand = myCommand;
                            DataSet ds = new DataSet(datasetName);
                            dAdapt.Fill(ds);
                            TypedDataTable tdt = new TypedDataTable(ds.Tables[tableName]);
                            if (fact.SelectSingleNode("@type").Value == "dataRow")
                            {
                                TypedDataRow tdr = new TypedDataRow(ds.Tables[tableName].Rows[0], tdt);
                                facts[i] = tdr;
                            }
                            else
                            {
                                facts[i] = tdt;
                            }
                            break;
                        }
                }
                i++;
            }

            // Execute Policy Tester
            try
            {
                policyTester.Execute(facts, dti);
            }
            catch (Exception e)
            {
                context.LogException(e);
                throw;
            }
            finally
            {
                dti.CloseTraceFile();
            }

            // write out all document instances passed in
            foreach (object fact in facts)
            {
                switch (fact.GetType().Name)
                {
                    case "TypedXmlDocument":
                        {
                            TypedXmlDocument txd = (TypedXmlDocument)fact;

                            context.LogData("TypedXmlDocument result: ", txd.Document.OuterXml);
                            MemoryStream data = StreamHelper.LoadMemoryStream(txd.Document.OuterXml);

                            // Validate if configured...
                            // HACK: We need to prevent ExecuteValidator for /each/ TypedXmlDocument
                            if (txd.DocumentType == "UBS.CLAS.PoC.Schemas.INSERTS")
                            {
                                context.ExecuteValidator(data, validationConfig);
                            }

                            break;
                        }
                    case "DataConnection":
                        {
                            DataConnection dc = (DataConnection)fact;
                            dc.Update(); // persist any changes
                            break;
                        }
                    case "TypedDataTable":
                        {
                            TypedDataTable tdt = (TypedDataTable)fact;
                            tdt.DataTable.AcceptChanges();
                            break;
                        }
                    case "TypedDataRow":
                        {
                            TypedDataRow tdr = (TypedDataRow)fact;
                            tdr.DataRow.AcceptChanges();
                            break;
                        }
                }
            }
        }
    }
}
